﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using System.Collections.Concurrent;

namespace example
{
    public class TestAssemblyLoadContext : AssemblyLoadContext
    {
        public TestAssemblyLoadContext() : base(true)
        {
        }
        protected override Assembly Load(AssemblyName name)
        {
            return null;
        }
    }

    class TestInfo
    {
        public TestInfo(MethodInfo mi)
        {
            entryPoint = mi;
        }
        MethodInfo entryPoint;
    }
    public struct LoadResource
    {
        public MethodInfo method;
        public TestAssemblyLoadContext loadContext;
    }

    class Program
    {
        //static TestInfo entryPoint;
        public static ConcurrentDictionary<string, LoadResource> LoadResourceDic
            = new ConcurrentDictionary<string, LoadResource>();
       
        [MethodImpl(MethodImplOptions.NoInlining)]
        static int ExecuteAndUnload(string assemblyPath, out WeakReference testAlcWeakRef)
        {
            var alc = new TestAssemblyLoadContext();
            LoadResourceDic.TryAdd("test", new LoadResource()
            {
                method = null,
                loadContext = alc
            });
           
            testAlcWeakRef = new WeakReference(alc);

            Assembly a = alc.LoadFromAssemblyPath(assemblyPath);
            if (a == null)
            {
                Console.WriteLine("Loading the test assembly failed");
                return -1;
            }

            var args = new object[1] { new string[] { "Hello" } };

            // Issue preventing unloading #1 - we keep MethodInfo of a method for an assembly loaded into the TestAssemblyLoadContext in a static variable
            //var entryPoint = new TestInfo(a.EntryPoint);
            //instanceDic.TryAdd("test",a.EntryPoint);
            //int result = (int)a.EntryPoint.Invoke(null, args);
            //testAssemblyLoadContextDic.TryRemove("test", out TestAssemblyLoadContext alcr);
            //instanceDic.TryRemove("test",out object ent);
            //alcr.Unload();

            //return result;
            return 0;
        }

        static void Main(string[] args)
        {
            WeakReference testAlcWeakRef;
            // Issue preventing unloading #2 - we keep MethodInfo of a method for an assembly loaded into the TestAssemblyLoadContext in a local variable
            //MethodInfo testEntryPoint;
            int result = ExecuteAndUnload(Path.Combine(AppContext.BaseDirectory, "../../../../test/bin/Debug/netcoreapp3.1/test.dll"), out testAlcWeakRef);

            for (int i = 0; testAlcWeakRef.IsAlive && (i < 10); i++)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }

            System.Diagnostics.Debugger.Break();

            Console.WriteLine($"Test completed, result={result}, unload success: {!testAlcWeakRef.IsAlive}");
        }
    }
}